/*
 * Drag & Drop for RhyJS
 * 
 * @author Tristan Maindron
 * @dependencies rhy.core.wrapper, rhy.core.view, rhy.core.browser, rhy.core.event, rhy.core.domready, rhy.core.config
 * 
 * The drag and drop engine convert every element made draggable to a movable 
 * absolute positionned element at the same location.
 */


Rhy.extra.drag = {
    // The object currently dragged.
    dragObject  : null,
    
    // The mouse current position.
    mouseOffset : null,
    
    // The object start position. Before dragging it.
    startLocation : null,
    
    // The object current location, during the drag.
    currentLocation: null,
    
    // The object drag end location.
    endLocation : null,
    
    // Some css used to disable highlighting during the drag.
    highlightCss : " -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -o-user-select: none; user-select: none;",
    
    // Get the coordinates on the page for a mouseEvent.
    mouseXY: function(ev){
        var b = document.body;
        if(ev.pageX || ev.pageY){
            return {x:ev.pageX, y:ev.pageY};
        }

        return {
            x:ev.clientX + b.scrollLeft - b.clientLeft,
            y:ev.clientY + b.scrollTop  - b.clientTop
        };
    },

    // Get the mouse position in an item (target) from an event.
    getMouseOffset: function(target, ev){
        ev = ev || window.event;

        var dpos = {
                    x : $r.vw.gp.getOffsetLeft(target), 
                    y : $r.vw.gp.getOffsetTop(target)
                };
        var mpos  = $r.ex.drag.mouseXY(ev);
        return {x:mpos.x - dpos.x, y:mpos.y - dpos.y};
    },

    // Called each time the mouse move.
    mouseMove: function(ev){
        ev           = ev || window.event;
        
        // Get the current dragged item if exists.
        var dro = $r.ex.drag.dragObject;
        
        // Get the mouse position.
        var mousePos = $r.ex.drag.mouseXY(ev);
        
        // If an item is dragged.
        if(dro){
            // Make the conversion to absolute element.
            dro.style.position = 'absolute';
            
            // We must kill the margin that will be taken into account in the top and left properties.
            dro.style.margin = '0';
            
            var both = dro.rhyDragConfig.axis == 'both';
            var value;

            // If the element can move along the y axis.
            if(dro.rhyDragConfig.axis == 'y' || both){
               // The top value is the mouse position substracted by the mouse offset.
               // And we make sure it's between the min and max value.
               value = Math.min(mousePos.y - $r.ex.drag.mouseOffset.y, dro.rhyDragConfig.y.max);
               value = Math.max(value, dro.rhyDragConfig.y.min);
               dro.style.top = value + 'px';
            }

            // If the element can move along the x axis.
            if(dro.rhyDragConfig.axis == 'x' || both){
                // The top value is the mouse position substracted by the mouse offset.
                // And we make sure it's between the min and max value.
                value = Math.min(mousePos.x - $r.ex.drag.mouseOffset.x, dro.rhyDragConfig.x.max);
                value = Math.max(value, dro.rhyDragConfig.x.min);
                dro.style.left = value + 'px';
            }
            
            // Actualize the dragged element current location.
            $r.ex.drag.currentLocation = {
                x : $r.vw.gp.getOffsetLeft(dro), 
                y : $r.vw.gp.getOffsetTop(dro)
            };
            
            // Call an user specified function in the configuration.
            dro.rhyDragConfig.onDrag(dro);
            
            // Make sure to keep the focus on the dragged element.
            dro.focus();
        }
    },

    // Called when the mouse button is released.
    mouseUp: function(ev){
        var dro = $r.ex.drag.dragObject;
        
        // If something is dragged.
        if(dro){
            // Get the mouse position.
            var mousePos = $r.ex.drag.mouseXY(ev);
            
            // Set the final drag position.
            $r.ex.drag.endLocation = {
                x : $r.vw.gp.getOffsetLeft(dro), 
                y : $r.vw.gp.getOffsetTop(dro)
            };
            
            // Retrieve a potential drop receiver. Null if no one found.
            var dpo = $r.ex.drop.get(mousePos.x, mousePos.y);
            
            // Call a user defined function for custom operations.
            dro.rhyDragConfig.onDrop(dro, dpo);
            
            // Release the dragged object.
            $r.ex.drag.dragObject = null;
            
            // Restablish the text highlights
            if($r.bw.getIExplorerVersion() > 8 || !$r.bw.isInternetExplorer())
                document.body.setAttribute('style',
                    document.body.getAttribute('style').replace($r.ex.drag.highlightCss, '')
                );
            document.body.setAttribute('unselectable', 'off');
            $r.off(document,'selectstart', $r.ex.drag.disableHl);
        }
    },

    mouseDown: function(ev){       
        // If the element is not marked as rhyDraggable, it means we clicked a child. So abord all operations.
        if(!$r.evt.source(ev).rhyDrag){
            return;
        }

        // Disable text selection (highlight) to prevent bad visual effects.
        if($r.bw.getIExplorerVersion() > 8 || !$r.bw.isInternetExplorer())
            document.body.setAttribute('style',
                document.body.getAttribute('style') + $r.ex.drag.highlightCss
            );
        document.body.setAttribute('unselectable', 'on');
        $r.on(document,'selectstart', $r.ex.drag.disableHl);

        // Catch the dragged object and save it.
        $r.ex.drag.dragObject  = $r.evt.source(ev)

        var dro = $r.ex.drag.dragObject;

        // Store the mouse offset in the element at the beginning of the drag.
        // It won't change during the drag because the element follows the mouse.
        $r.ex.drag.mouseOffset = $r.ex.drag.getMouseOffset(dro, ev);

        // Store the start location.
        $r.ex.drag.startLocation = {
            x : $r.vw.gp.getOffsetLeft(dro), 
            y : $r.vw.gp.getOffsetTop(dro)
        };

        // Call the user defined function for the drag beginning.
        dro.rhyDragConfig.onDragBegin(dro);

        return false;
    },

    // Callback to highlight event, kills the event propagation to prevent text selection during the drag.
    disableHl: function(e){
        $r.evt.kill(e);
        return false;
    },


    // Must be applied to an object to make it draggable.
    apply: function(item, config){
        if(item){
            
            // Store the dragConfig into the element in order to use it anywhere.
            item.rhyDragConfig = $r.cfg.mgr.merge({
                onDragBegin: function(){},
                onDrag:   function(){},
                onDrop:   function(){},
                axis : "both", 
                x : { min : -1000, max: 10000},
                y : { min : -1000, max: 10000}
            }, config);
            
            // Mark the element as rhyDraggable.
            item.rhyDrag = true;
            
            // Make the element listen the mouse button pressed event.
            $r.on(item, 'mousedown', $r.ex.drag.mouseDown);
            
        }
    }
}

Rhy.onDocumentReady(function(){
    $r.on(document, 'mouseup', $r.ex.drag.mouseUp);
    $r.on(document, 'mousemove', $r.ex.drag.mouseMove); 
});

Rhy.extra.drop = {
    lst : [],
    
    // Makes an element dropable.
    apply: function(item, config){
       item.dropConfig = $r.cfg.mgr.merge({}, config);
       $r.ex.drop.lst.push(item); 
    },
    
    // Return the first dropable element intersecting the given absolute position.
    // The element must NOT be affected by display:none .
    get: function(x, y){
        for(var i=0; i < $r.ex.drop.lst.length; i++){
            var item = $r.ex.drop.lst[i];
            var minX = $r.vw.gp.getOffsetLeft(item);
            var minY = $r.vw.gp.getOffsetTop(item);
            var maxX = minX + $r.vw.gp.getOffsetWidth(item);
            var maxY = minY + $r.vw.gp.getOffsetHeight(item);
            
            if(x > minX && x < maxX && y > minY && y < maxY){
                return item;
            }
        }
        
        return null;
    }
}